import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Observable;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
import net.miginfocom.swing.MigLayout;

public class SudGUI implements SudokuUI {
	
	private SudokuGame game;
	private int size;
	private JFrame sudokuFrame;
	private JPanel contentPane;
	private JPanel sudokuPanel;
	private JPanel helpPanel;
	private JButton[][] square;
	private JButton resetButton;
	private JButton quitButton;
	private JButton helpButton;
	private String colSize;
	private String rowSize;
	
	/**
	 * Constructor for the class which sets the game object and the size for this game of Sudoku
	 * @param game  This instance of the Sudoku game
	 * @param size  This size of the grid for this Sudoku game
	 */
	public SudGUI(SudokuGame game, int size) {
		
		this.game = game;
		this.size = size;
	}
	
	/**
	 * Resets the game and the board display.
	 */
	public void resetBoard() {
		
		game.resetBoard();
		sudokuFrame.dispose();
		startUI();
	}
	
	/**
	 * Sets up a new game and board display
	 */
	private void newGame() {
		
		Object[] lvlOptions = {"Easy", "Medium", "Hard"};
		
		String lvlChoice = (String)JOptionPane.showInputDialog(null, "Please select a level of difficulty:", "Difficulty", JOptionPane.QUESTION_MESSAGE, null, lvlOptions, lvlOptions[0]);
		
		if (lvlChoice != null) {
			
			game = new SudokuGame(size, lvlChoice);
			game.addObserver(this);
		}
		
		else {
			
			if (playAgain()) {
				
				newGame();
			}
			
			else {
				
				System.exit(0);
			}
		}
		
		game.newGame();
		sudokuFrame.dispose();
		startUI();
	}
	
	/**
	 * Calls the methods to build and display the graphical interface
	 */
	public void startUI() {
		
		createGUI();
		sudokuFrame.pack();
		showBoard();
	}
	
	/**
	 * Displays the board.
	 */
	public void showBoard() {
		
		sudokuFrame.setVisible(true);
	}
	
	/**
	 * Builds the graphical interface using MigLayout.
	 */
	public void createGUI() {
		
		setSpacing();
		MigLayout sudokuLayout = new MigLayout("", colSize, rowSize);
		sudokuFrame = new JFrame("Kat's Sudoku Game");
		sudokuPanel = new JPanel(sudokuLayout);
		helpPanel = new JPanel(new MigLayout());
		contentPane = new JPanel();
		contentPane.setLayout(new MigLayout());
		sudokuFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		
		square = new JButton[9][9];
		resetButton = new JButton("Reset");
		resetButton.addActionListener(new resetListener());
		quitButton = new JButton("Quit");
		quitButton.addActionListener(new quitListener());
		helpButton = new JButton("Help");
		helpButton.addActionListener(new helpListener());
		
		for (int r = 0; r < size; r++) {
			for (int c = 0; c < size; c++) {
				
				square[r][c] = new JButton(Integer.toString(game.getSquare(r, c)));
				square[r][c].addActionListener(new squareListener(r, c)); // Add actionListeners
				square[r][c].setToolTipText(game.getPossible(r, c));
				
				if (c == (size - 1)) { // minus one to account for being the last button in this row
					
					sudokuPanel.add(square[r][c], "wrap");
				}
				
				else {
					
					sudokuPanel.add(square[r][c]);
				}
			}
		}
		
		helpPanel.add(resetButton);
		helpPanel.add(quitButton);
		helpPanel.add(helpButton);
		
		sudokuFrame.setContentPane(contentPane);
		
		contentPane.add(sudokuPanel, "wrap");
		contentPane.add(helpPanel);		
	}
	
	/**
	 * Sets the spacing that should appear between each row of sections and each column of sections
	 * when drawing the Sudoku board.
	 * Creates a String because MigLayout uses String inputs.
	 */
	public void setSpacing() {
		
		StringBuilder SBcolSize = new StringBuilder();
		StringBuilder SBrowSize = new StringBuilder();
		
		for (int s = 1; s <= Math.sqrt(size); s++) {
			for(int rc = 1; rc <= Math.sqrt(size); rc++) {
				
				SBcolSize.append("[]");
				SBrowSize.append("[]");
			}
			
			if (s < Math.sqrt(size)) {
				
				SBcolSize.append("20");
				SBrowSize.append("20");
			}
		}
		
		colSize = SBcolSize.toString();
		rowSize = SBrowSize.toString();
	}
	
	/**
	 * Displays congratulations for winning the game.
	 */
	public void showWin() {
		
		JOptionPane.showMessageDialog(null, "Congratulations! You have beat the game!", "You Win!", JOptionPane.PLAIN_MESSAGE);
	}
	
	/**
	 * Checks if the user would like to play another game of Sudoku.
	 * @return  True if the user would like to play another round or false if they do not
	 */
	public boolean playAgain() {
		
		int choice = JOptionPane.showConfirmDialog(null, "Would you like to play again?", "More Sudoku?", JOptionPane.YES_NO_OPTION);
		
		if (choice == JOptionPane.YES_OPTION) {
			
			return true;
		}
		
		else {
			
			return false;
		}
	}
	
	/**
	 * Displays the possible values for a square and receives a value from the player
	 * @param row  Row of this square
	 * @param col  Column of this square
	 * @return  String value representing the number the player would like entered for this square
	 */
	private String inputGuess(int row, int col) {
		
		return JOptionPane.showInputDialog(null, game.getPossible(row, col));
	}
	
	/**
	 * Resets the list of possible values for each square that
	 * appears as a tooltip when each square is hovered over.
	 */
	private void resetPossibilities() {
		
		for (int r = 0; r < size; r++) {
			for (int c = 0; c < size; c++) {
				
				square[r][c].setToolTipText(game.getPossible(r, c));
			}
		}
		
	}
	
	/**
	 * Observer method, notified when the Observable object updates
	 */
	public void update(Observable ob, Object obj) {
		
		resetPossibilities();
		checkGameStatus();
	}
	
	/**
	 * Checks to see if the game is won.
	 * If the game is won, it sends a congratulation message and if the player
	 * wants to play again, it resets the gameboard.
	 */
	public void checkGameStatus() {
		
		if (game.isGameWon()) {
			
			showWin();
			
			if (playAgain()) {
				
				newGame();
			}
		}	
	}
	
	/**
	 * Presents an input dialog to the player to receive the player's value for the given square.
	 * If the player cancels out of the dialog, the value for the square does not change.
	 * IF the player inputs an invalid number or any invalid input, an error is generated and the value
	 * of the square does not change.
	 * @param row  Row of this square
	 * @param col  Column of this square
	 * @param square  JButton object that the player clicked
	 * @return  Integer value that the player selects for this square
	 */
	public int guessDialog(int row, int col, JButton square) {
		
		String guess = inputGuess(row, col);
		
		int i_guess;
		
		if (guess == null) {
			
			guess = square.getText();
		}
		
		try {
			
			i_guess = Integer.parseInt(guess);
		
		} catch (NumberFormatException nfe){
			
			JOptionPane.showMessageDialog(null, "Invalid Input. Please enter a number between 0 and " + size, "ERROR", JOptionPane.ERROR_MESSAGE);
			i_guess = Integer.parseInt(square.getText());
		}
		
		return i_guess;
	}
	
	/**
	 * Listens to each button and sends information to the game class
	 * to be evaluated.
	 * @author Kat.Winter
	 */
	class squareListener implements ActionListener {
		
		int row;
		int col;
		
		public squareListener(int r, int c) {
			row = r;
			col = c;
		}
		
		public void actionPerformed(ActionEvent evt) {
			
			game.validateInput(row, col);
			
			if (!game.hasError()) {
				
				JButton square = (JButton)evt.getSource();
				
				int i_guess = guessDialog(row, col, square);
				
//NOTE..........Does not process if the square's value hasn't changed because the player canceled input
				if (i_guess != Integer.parseInt(square.getText())) { 
				
					game.validateGuess(row, col, i_guess);
				
					if (!game.hasError()) {
				
						square.setText(Integer.toString(i_guess));						
						game.processGuess(row, col, i_guess);
					}
				}
			}
			
			if (game.hasError()) {
				
				JOptionPane.showMessageDialog(null, game.getError(), "ERROR", JOptionPane.ERROR_MESSAGE);
			}
		}
	}
	
	/**
	 * Listener for the Reset button. If this button is pressed
	 * the board is reset to initial values.
	 * @author kat.winter
	 *
	 */
	class resetListener implements ActionListener {
		
		public void actionPerformed(ActionEvent evt) {

			resetBoard();
		}
		
	}
	
	/**
	 * Listener for the Quit button which prompts the player to play again.
	 * If they want to play again, the game is restarted.
	 * If they want to quit, the application closes.
	 * @author kat.winter
	 *
	 */
	class quitListener implements ActionListener {
		
		public void actionPerformed (ActionEvent evt) {
			
			if (playAgain()) {
				
				newGame();
			}
			
			else {
				
				System.exit(0);
			}
		}
	}
	
	/**
	 * Listener for the Help Button which displays help information
	 * @author kat.winter
	 */
	class helpListener implements ActionListener {
		
		public void actionPerformed (ActionEvent evt) {
			
			JOptionPane.showMessageDialog(null, "Press a square to enter its value. Enter a value of 0 to 'reset' the square.\nPress Reset to reset all squares and press Quit to quit.", "Halp!", JOptionPane.OK_OPTION);
		}
	}
}
